import { _decorator, Component, Node, tween, v3 } from 'cc';
import { DEBUG } from 'cc/env';
import AssetModule from 'db://cocos-ext/base/AssetModule';
import EventExt from 'db://cocos-ext/base/EventExt';
import { UtilCocos } from 'db://cocos-ext/base/UtilCocos';

import ComboModel from '../define/ComboModel';
import GameManager from '../define/GameManager';
import TileModel from '../define/TileModel';
import Train from '../define/Train';
import { Direction, EVENT_TILE_CLICK, EVENT_TILE_MOVE, UIConfig } from '../define/UIConfig';
import Tile from './Tile';

const { ccclass, property } = _decorator;

@ccclass('TileCtrl')
export default class TileCtrl extends Component {

    // parent
    @property(Node)
    private panel: Node = null;

    // 选中标识
    @property(Node)
    private selTag: Node = null;


    // 组件表：二维数组，保存所有方块 Tile 组件，方便读取
    private tileMap: Tile[][] = null;
    // 当前已经选中的方块坐标
    private selXY: Train = null;
    // 可消除组合
    private combos: ComboModel[] = null;

    protected onLoad() {
        this.selTag.active = false;
        EventExt.on(EVENT_TILE_CLICK, this.onTileClicked, this);
        EventExt.on(EVENT_TILE_MOVE, this.onTileMoved, this);

        // 生产初始化随机方块
        this.generateTiles(GameManager.createNewMap());
    }

    /**
    * 点击色块
    * @param a 坐标
    */
    private onTileClicked(a: Train) {
        if (!a) return;
        DEBUG && console.log('点击 | 坐标: ' + a.toString() + ' | type: ' + this.getType(a));
        if (!!this.selXY && !this.selXY.isSame(a) && this.selXY.isNearby(a)) {
            this.tryExchangeByTouch(this.selXY, a);
            this.setSelectedTile(null);
        }
        else {
            this.setSelectedTile(a);
        }
    }

    /**
    * 移动色块
    * @param a 坐标
    * @param dir 方向
    */
    private onTileMoved(a: Train, dir: Direction) {
        if (!a || !dir) return;
        this.tryExchangeBySlid(a, dir);
    }


    /**
    * 设置选中的方块
    * @param a 坐标
    */
    private setSelectedTile(a: Train) {
        this.selXY = a;
        if (a) {
            this.selTag.active = true;
            this.selTag.setPosition(TileModel.getPos(a));
        } else {
            this.selTag.active = false;
        }
    }

    /**
    * 尝试点击交换方块
    * @param a
    * @param b
    */
    private tryExchangeByTouch(a: Train, b: Train) {
        DEBUG && console.log('尝试点击交换方块 | a: ' + a.string + ' | b: ' + b.string);
        this.tryExchange(a, b);
    }

    /**
    * 尝试滑动交换方块
    * @param xy 坐标
    * @param direction 方向
    */
    private tryExchangeBySlid(xy: Train, direction: Direction) {
        DEBUG && console.log('点击交换方块 | 坐标: ' + xy.toString() + ' | direction: ' + direction);
        const tarXY = xy.getNearbyXY(direction);
        if (tarXY) {
            this.tryExchange(xy, tarXY);
            this.setSelectedTile(null);
        }
    }

    /**
    * 尝试交换方块
    * @param a 1
    * @param b 2
    */
    private async tryExchange(a: Train, b: Train) {
        // 交换方块
        await this.exchangeTiles(a, b);
        // 获取可消除组合
        this.combos = ComboModel.getCombos(GameManager.typeMap);
        if (this.combos.length > 0) {
            await new Promise(res => setTimeout(res, 250));
            this.removeCombos(); // 消除
            await new Promise(res => setTimeout(res, 250));
            await this.falldown(); // 下落
            await new Promise(res => setTimeout(res, 250));
            await this.fillEmpty(); // 填充
            await new Promise(res => setTimeout(res, 250));
            this.checkAndRomoveCombo(); // 持续检测
        } else {
            // 不能消除，换回来吧
            await this.exchangeTiles(a, b);
        }
    }

    /**
    * 交换方块
    * @param a
    * @param b
    */
    private async exchangeTiles(a: Train, b: Train) {
        // 保存变量
        let tile1 = this.getTile(a);
        let tile2 = this.getTile(b);
        let tile1Type = this.getType(a);
        let tile2Type = this.getType(b);
        // 交换数据
        tile1.setXY(b);
        tile2.setXY(a);
        GameManager.setType(a, tile2Type);
        GameManager.setType(b, tile1Type);
        this.setTile(a, tile2);
        this.setTile(b, tile1);
        // 交换方块动画
        tween(tile1.node).to(0.1, { position: TileModel.getPos(b) }).start();
        tween(tile2.node).to(0.1, { position: TileModel.getPos(a) }).start();
        await new Promise(res => setTimeout(res, 100));
    }

    // ----------------------------------------------------------------------------------------------------
    /**
    * 消除组合
    */
    private removeCombos() {
        for (let i = 0; i < this.combos.length; i++) {
            for (let j = 0; j < this.combos[i].xySet.length; j++) {
                this.removeTile(this.combos[i].xySet[j]);
            }
        }
        this.combos = [];
    }

    /**
    * 检查可消除组合直到没有可以消除的组合
    */
    private async checkAndRomoveCombo() {
        this.combos = ComboModel.getCombos(GameManager.typeMap); // 获取可消除的组合
        // 有可消除的组合吗
        while (this.combos.length > 0) {
            this.removeCombos(); // 消除
            await new Promise(res => setTimeout(res, 250));
            await this.falldown(); // 下落
            await new Promise(res => setTimeout(res, 250));
            await this.fillEmpty(); // 填充
            await new Promise(res => setTimeout(res, 250));
            this.combos = ComboModel.getCombos(GameManager.typeMap); // 获取可消除的组合
            await new Promise(res => setTimeout(res, 250));
        }
        // 存在一步可消除情况吗
        if (!this.hasValidCombo(GameManager.typeMap)) {
            this.removeAllTiles(); // 移除所有方块
            this.generateTiles(GameManager.typeMap); // 生成方块
        }
    }

    /**
    * 移除所有方块
    */
    private removeAllTiles() {
        for (let i = 0; i < this.tileMap.length; i++) {
            for (let j = 0; j < this.tileMap[i].length; j++) {
                this.getTile(i, j).hide();
                this.setTile(i, j, null);
            }
        }
        GameManager.clearMap();
    }

    /**
    * 消除方块
    * @param a 坐标
    */
    private removeTile(a: Train) {
        this.getTile(a).hide(); // 方块消失
        // 数据置空
        this.setTile(a, null);
        GameManager.setType(a, -1);
    }

    // ----------------------------------------------------------------------------------------------------
    /**
    * 方块下落
    */
    private async falldown() {
        let promises: Promise<void>[] = [];
        for (let c = 0; c < UIConfig.COL; c++) {
            for (let r = 0; r < UIConfig.ROW; r++) {
                // 找到空位
                if (!this.getType(c, r)) {
                    // 往上找方块
                    for (let nr = r + 1; nr < UIConfig.ROW; nr++) {
                        // 找到可以用的方块
                        if (this.getType(c, nr)) {
                            // 转移数据
                            GameManager.setType(c, r, this.getType(c, nr));
                            this.setTile(c, r, this.getTile(c, nr));
                            this.getTile(c, r).setXY(c, r);
                            // 置空
                            this.setTile(c, nr, null);
                            GameManager.setType(c, nr, undefined);
                            // 下落
                            let fallPos = TileModel.getPos(c, r);
                            let fallTime = (nr - r) * 0.1;
                            promises.push(new Promise(res => {
                                tween(this.getTile(c, r).node)
                                    .to(fallTime, { position: v3(fallPos.x, fallPos.y - 10) })
                                    .to(0.05, { position: fallPos })
                                    .call(() => res())
                                    .start();
                            }));
                            break;
                        }
                    }
                }
            }
        }
        // 等待所有方块完成下落动画
        await Promise.all(promises);
    }

    /**
    * 填充空位
    */
    private async fillEmpty() {
        for (let c = 0; c < UIConfig.COL; c++) {
            for (let r = 0; r < UIConfig.ROW; r++) {
                // 找到空位
                if (!this.getType(c, r)) {
                    let type = GameManager.getRandomType();
                    let tile = this.getNewTile(c, r, type);
                    this.setTile(c, r, tile);
                    GameManager.setType(c, r, type);
                }
            }
        }
        await new Promise(res => setTimeout(res, 100));
    }

    // ----------------------------------------------------------------------------------------------------
    /**
    * 设置类型表
    * @param x 横坐标
    * @param y 纵坐标
    */
    private getType(x: number | Train, y?: number): number {
        return typeof x === 'number' ? GameManager.typeMap[x][y] : GameManager.typeMap[x.x][x.y];
    }



    /**
    * 获取组件
    * @param x 横坐标
    * @param y 纵坐标
    */
    private getTile(x: number | Train, y?: number): Tile {
        const tile = typeof x === 'number' ? this.tileMap[x][y] : this.tileMap[x.x][x.y];
        if (DEBUG && !tile) {
            // FIXME
            console.log("[TileCtrl] 调试tile为空问题，坐标", x, y);
        }
        return tile;
    }

    /**
    * 设置组件表
    * @param x 横坐标
    * @param y 纵坐标
    * @param type 组件
    */
    private setTile(x: number | Train, y: number | Tile, tile?: Tile) {
        if (typeof x === 'number')
            this.tileMap[x][<number>y] = tile;
        else
            this.tileMap[x.x][x.y] = y as Tile;
    }

    // ----------------------------------------------------------------------------------------------------



    /**
    * 根据类型表生成方块
    */
    private generateTiles(typeMap: number[][]) {
        this.tileMap = [];
        for (let c = 0; c < UIConfig.COL; c++) {
            let colTileSet: Tile[] = [];
            for (let r = 0; r < UIConfig.ROW; r++) {
                colTileSet.push(this.getNewTile(c, r, typeMap[c][r]));
            }
            this.tileMap.push(colTileSet);
        }
    }

    /**
    * 生成并初始化方块
    * @param x 横坐标
    * @param y 纵坐标
    * @param type 类型
    */
    private getNewTile(x: number, y: number, type: number): Tile {
        const node = AssetModule.getPrefabNode("tile");
        node.setParent(this.panel);
        node.setPosition(TileModel.getPos(x, y));
        UtilCocos.setNodeSize(node, UIConfig.tileSize());
        const tile = node.getComponent(Tile);
        tile.setXY(x, y);
        tile.setType(type);
        tile.show();
        return tile;
    }

    /**
    * 是否有可一步消除的组合
    */
    public hasValidCombo(map: number[][]) {
        for (let r = 0; r < UIConfig.ROW; r++) {
            for (let c = 0; c < UIConfig.COL; c++) {
                if (c + 3 < UIConfig.COL) {
                    if (map[c][r] === map[c + 1][r] && map[c][r] === map[c + 3][r]) { // 1 1 X 1
                        return true;
                    }
                    if (map[c][r] === map[c + 2][r] && map[c][r] === map[c + 3][r]) { // 1 X 1 1
                        return true;
                    }
                }
                if (c + 2 < UIConfig.COL) {
                    if (map[c][r] === map[c + 1][r]) {
                        if (r - 1 >= 0 && map[c][r] === map[c + 2][r - 1]) { // 1 1 X
                            return true;                                     // X X 1
                        }
                        if (r + 1 < UIConfig.ROW && map[c][r] === map[c + 2][r + 1]) { // X X 1
                            return true;                                                 // 1 1 X
                        }
                    }
                    if (map[c][r] === map[c + 2][r]) {
                        if (r - 1 >= 0 && map[c][r] === map[c + 1][r - 1]) { // 1 X 1
                            return true;                                     // X 1 X
                        }
                        if (r + 1 < UIConfig.ROW && map[c][r] === map[c + 1][r + 1]) { // X 1 X
                            return true;                                                 // 1 X 1
                        }
                    }
                    if (r - 1 >= 0 &&
                        map[c][r] === map[c + 1][r - 1] && map[c + 1][r - 1] === map[c + 2][r - 1]) { // 1 X X
                        return true;                                                                  // X 1 1
                    }
                    if (r + 1 < UIConfig.ROW &&
                        map[c][r] === map[c + 1][r + 1] && map[c + 1][r + 1] === map[c + 2][r + 1]) { // X 1 1
                        return true;                                                                  // 1 X X
                    }
                }

                if (r + 3 < UIConfig.ROW) {
                    if (map[c][r] === map[c][r + 1] && map[c][r] === map[c][r + 3]) {
                        return true;
                    }
                    if (map[c][r] === map[c][r + 2] && map[c][r] === map[c][r + 3]) {
                        return true;
                    }
                }
                if (r + 2 < UIConfig.ROW) {
                    if (map[c][r] === map[c][r + 1]) {
                        if (c - 1 >= 0 && map[c][r] === map[c - 1][r + 2]) {
                            return true;
                        }
                        if (c + 1 < UIConfig.COL && map[c][r] === map[c + 1][r + 2]) {
                            return true;
                        }
                    }
                    if (map[c][r] === map[c][r + 2]) {
                        if (c - 1 >= 0 && map[c][r] === map[c - 1][r + 1]) {
                            return true;
                        }
                        if (c + 1 < UIConfig.COL && map[c][r] === map[c + 1][r + 1]) {
                            return true;
                        }
                    }
                    if (c - 1 >= 0 &&
                        map[c][r] === map[c - 1][r + 1] && map[c - 1][r + 1] === map[c - 1][r + 2]) {
                        return true;
                    }
                    if (c + 1 < UIConfig.COL &&
                        map[c][r] === map[c + 1][r + 1] && map[c + 1][r + 1] === map[c + 1][r + 2]) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    
}
